基于redis的cas ticket共享

实现cas ticket基于redis的集群

目的

克服cas单点故障,将cas认证请求分发到多台cas服务器上,降低负载。

实现思路:

采用统一的ticket存取策略,所有ticket的操作都从中央缓存redis中存取。
采用session共享,session的存取都从中央缓存redis中存取。

前提:

这里只讲解如何实现cas ticket的共享,关于session的共享请移步:

实现步骤:

  1. 基于cas源码 新增模块 cas-server-integration-redis

    pom.xml 文件如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    	
    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
    <artifactId>cas-server</artifactId>
    <groupId>org.jasig.cas</groupId>
    <version>4.0.2</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>cas-server-integration-redis</artifactId>

    <dependencies>

    <dependency>
    <groupId>org.jasig.cas</groupId>
    <artifactId>cas-server-core</artifactId>
    <version>${project.version}</version>
    </dependency>

    <dependency>
    <groupId>org.jasig.cas</groupId>
    <artifactId>cas-server-support-saml</artifactId>
    <version>${project.version}</version>
    <scope>provided</scope>
    </dependency>


    <!-- redis -->
    <dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-redis</artifactId>
    <version>1.5.1.RELEASE</version>
    </dependency>
    <dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
    <version>2.2</version>
    </dependency>
    <dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>2.6.2</version>
    </dependency>
    </dependencies>

    </project>
  2. 添加类到 cas-server-integration-redis 模块的 org.jasig.cas.ticket.registry 包下

    RedisTicketRegistry.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
/*
* Licensed to Jasig under one or more contributor license
* agreements. See the NOTICE file distributed with this work
* for additional information regarding copyright ownership.
* Jasig licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a
* copy of the License at the following location:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.jasig.cas.ticket.registry;

import org.jasig.cas.ticket.ServiceTicket;
import org.jasig.cas.ticket.Ticket;
import org.jasig.cas.ticket.TicketGrantingTicket;
import org.springframework.beans.factory.DisposableBean;

import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.TimeUnit;

/**
* Key-value ticket registry implementation that stores tickets in redis keyed on the ticket ID.
*
* @author Scott Battaglia
* @author Marvin S. Addison
* @since 3.3
*/
public final class RedisTicketRegistry extends AbstractDistributedTicketRegistry implements DisposableBean {

private final static String TICKET_PREFIX = "TICKETGRANTINGTICKET:";

/** redis client. */
@NotNull
private final TicketRedisTemplate client;

/**
* TGT cache entry timeout in seconds.
*/
@Min(0)
private final int tgtTimeout;

/**
* ST cache entry timeout in seconds.
*/
@Min(0)
private final int stTimeout;


/**
* Creates a new instance using the given redis client instance, which is presumably configured via
* <code>net.spy.redis.spring.redisClientFactoryBean</code>.
*
* @param client redis client.
* @param ticketGrantingTicketTimeOut TGT timeout in seconds.
* @param serviceTicketTimeOut ST timeout in seconds.
*/
public RedisTicketRegistry(final TicketRedisTemplate client, final int ticketGrantingTicketTimeOut,
final int serviceTicketTimeOut) {
this.tgtTimeout = ticketGrantingTicketTimeOut;
this.stTimeout = serviceTicketTimeOut;
this.client = client;
}

protected void updateTicket(final Ticket ticket) {
logger.debug("Updating ticket {}", ticket);
try {
this.client.boundValueOps(TICKET_PREFIX+ticket.getId()).set(ticket,getTimeout(ticket), TimeUnit.SECONDS);
} catch (final Exception e) {
logger.error("Failed updating {}", ticket, e);
}
}

public void addTicket(final Ticket ticket) {
logger.debug("Adding ticket {}", ticket);
try {
this.client.boundValueOps(TICKET_PREFIX+ticket.getId()).set(ticket,getTimeout(ticket),TimeUnit.SECONDS);
}catch (final Exception e) {
logger.error("Failed adding {}", ticket, e);
}
}

public boolean deleteTicket(final String ticketId) {
logger.debug("Deleting ticket {}", ticketId);
try {
this.client.delete(TICKET_PREFIX+ticketId);
return true;
} catch (final Exception e) {
logger.error("Failed deleting {}", ticketId, e);
}
return false;
}

public Ticket getTicket(final String ticketId) {
try {
final Ticket t = (Ticket) this.client.boundValueOps(TICKET_PREFIX+ticketId).get();
if (t != null) {
return getProxiedTicketInstance(t);
}
} catch (final Exception e) {
logger.error("Failed fetching {} ", ticketId, e);
}
return null;
}

/**
* {@inheritDoc}
* This operation is not supported.
*
* @throws UnsupportedOperationException if you try and call this operation.
*/
@Override
public Collection<Ticket> getTickets() {
Set<Ticket> tickets = new HashSet<Ticket>();
Set<String> keys = this.client.keys(TICKET_PREFIX + "*");
for (String key:keys){
Ticket ticket = this.client.boundValueOps(TICKET_PREFIX+key).get();
if(ticket==null){
this.client.delete(TICKET_PREFIX+key);
}else{
tickets.add(ticket);
}
}
return tickets;
}

public void destroy() throws Exception {
//do nothing
}

/**
* @param sync set to true, if updates to registry are to be synchronized
* @deprecated As of version 3.5, this operation has no effect since async writes can cause registry consistency issues.
*/
@Deprecated
public void setSynchronizeUpdatesToRegistry(final boolean sync) {}

@Override
protected boolean needsCallback() {
return true;
}

private int getTimeout(final Ticket t) {
if (t instanceof TicketGrantingTicket) {
return this.tgtTimeout;
} else if (t instanceof ServiceTicket) {
return this.stTimeout;
}
throw new IllegalArgumentException("Invalid ticket type");
}
}
**TicketRedisTemplate.java**
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package org.jasig.cas.ticket.registry;

import org.jasig.cas.ticket.Ticket;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

/**
* Created by serv on 2015/7/19.
*/
public class TicketRedisTemplate extends RedisTemplate<String, Ticket> {

public TicketRedisTemplate() {
RedisSerializer<String> string = new StringRedisSerializer();
JdkSerializationRedisSerializer jdk = new JdkSerializationRedisSerializer();
setKeySerializer(string);
setValueSerializer(jdk);
setHashKeySerializer(string);
setHashValueSerializer(jdk);
}

public TicketRedisTemplate(RedisConnectionFactory connectionFactory) {
this();
setConnectionFactory(connectionFactory);
afterPropertiesSet();
}
}
  1. cas-server-webapp 添加刚才新增模块的依赖

    1
    2
    3
    4
    5
    <dependency>
    <groupId>org.jasig.cas</groupId>
    <artifactId>cas-server-integration-redis</artifactId>
    <version>${project.version}</version>
    </dependency>
  2. 修改 spring配置文件 cas-server-webapp 模块 WEB-INF\spring-configuration\ticketRegistry.xml

    {% codeblock %}
    		
    		{% endcodeblock %}
    

    替换为

    {% codeblock %}
    		
    	        
    	
    	        
    	        
    	
    	        
    	        
    	    
    	
    	    
    	
    	    
    		{% endcodeblock %}
    

    注意: 里面的 jedisConnFactory链接信息 修改为自己的连接串,这里选择database 1为存放cas票据的数据库

  1. 重新编译 mvn install

    生成的cas.war 部署到多个已经做过session共享的tomcat容器中。